﻿using FluentValidation;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NSwag.Annotations;
using ProxyServer.Entities;
using ProxyServer.EntityFrameworkCore;

namespace ProxyServer.Endpoints;

public record EndpointDto(string Name, string Address);
public record ServiceDto(Guid Id, string Name, string? MatchPath, List<string> Hosts, List<string>? Methods, DateTimeOffset LastModified, List<string> Endpoints, string? RouteOverwrite);
public record SaveServiceRequest(Guid? Id, string Name, string? MatchPath, List<string> Hosts, List<string> Methods, List<string> Endpoints, string? RouteOverwrite);

public class SaveServiceRequestValidator : AbstractValidator<SaveServiceRequest>
{
    public static readonly SaveServiceRequestValidator Instance = new SaveServiceRequestValidator();

    public SaveServiceRequestValidator()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("Service name is required.");
        RuleFor(x => x.Hosts).NotEmpty().WithMessage("At least one host is required.");
        RuleFor(x => x.Endpoints).NotEmpty().WithMessage("At least one endpoint is required.");
    }
}

public static class ServiceEndpoints
{
    static Task<bool> ExistsServiceNameAsync(this AppDbContext db, string name, CancellationToken cancellationToken)
    {
        return db.Set<ServiceEntity>().AnyAsync(x => x.Name == name, cancellationToken);
    }

    [OpenApiOperation(nameof(DeleteService), "", "")]
    public static async Task DeleteService([FromRoute] Guid id, [FromServices] AppDbContext db, CancellationToken cancellationToken)
    {
        var service = await db.Set<ServiceEntity>().FindAsync(new object[] { id }, cancellationToken);
        if (service == null)
        {
            throw new ArgumentException($"Service with ID {id} does not exist.");
        }
        db.Remove(service);
        await db.SaveChangesAsync(cancellationToken);
    }

    [OpenApiOperation(nameof(SaveService), "", "")]
    public static async Task SaveService([FromServices] AppDbContext db, [FromBody] SaveServiceRequest request, CancellationToken cancellationToken)
    {
        var validationResult = await SaveServiceRequestValidator.Instance.ValidateAsync(request, cancellationToken);
        if (validationResult.IsValid == false)
        {
            throw new ArgumentException(string.Join(", ", validationResult.Errors.Select(x => x.ErrorMessage)));
        }

        if (request.Id is not null)
        {
            await UpdateService(db, request, cancellationToken);
            return;
        }

        if (await db.ExistsServiceNameAsync(request.Name, cancellationToken))
        {
            throw new ArgumentException($"service name {request.Name} exists");
        }

        var service = new ServiceEntity(request.Name, request.Hosts)
        {
            MatchPath = request.MatchPath,
            Methods = request.Methods,
            AcmeOrderId = Guid.NewGuid(),
            RouteOverwrite = request.RouteOverwrite
        };

        foreach (var (addr, index) in request.Endpoints.WithIndex())
        {
            var endpoint = new ServiceEndpointEntity(FormatEndpointName(request.Name, index), addr);

            service.Endpoints.Add(endpoint);
        }

        db.Add(service);
        await db.SaveChangesAsync(cancellationToken);
    }

    private static async Task UpdateService(AppDbContext db, SaveServiceRequest request, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(request.Id, "SaveServiceRequest.Id");

        var current = await db.Set<ServiceEntity>().FindAsync(new object[] { request.Id.Value }, cancellationToken);

        if (current == null)
        {
            throw new ArgumentException($"Service with ID {request.Id} does not exist.");
        }

        if (current.Name != request.Name)
        {
            var repeated = await db.Set<ServiceEntity>().AnyAsync(s => s.Name == request.Name && s.Id != request.Id, cancellationToken);
            if (repeated)
            {
                throw new InvalidOperationException($"Repeat service name '{request.Name}'");
            }
        }

        current.Name = request.Name;
        current.MatchPath = request.MatchPath;
        current.Hosts = request.Hosts;
        current.Methods = request.Methods;
        current.LastModified = DateTimeOffset.UtcNow;

        current.Endpoints = request.Endpoints.Select((addr, index) => new ServiceEndpointEntity(FormatEndpointName(request.Name, index), addr)).ToList();
        current.RouteOverwrite = request.RouteOverwrite;

        await db.SaveChangesAsync(cancellationToken);
    }

    private static string FormatEndpointName(string name, int index)
    {
        return $"{name}_{index:D4}";
    }

    [OpenApiOperation(nameof(ListAllServices), "", "")]
    public static async Task<Ok<List<ServiceDto>>> ListAllServices([FromServices] AppDbContext db, CancellationToken cancellationToken)
    {
        var results = await db.Set<ServiceEntity>().ToListAsync(cancellationToken);

        return TypedResults.Ok(results
                .Select(x => new ServiceDto(x.Id, x.Name, x.MatchPath, x.Hosts, x.Methods, x.LastModified, x.Endpoints.Select(y => y.Address).ToList(), x.RouteOverwrite))
                .ToList());
    }
}

public static class EnumerableExtensions
{
    public static IEnumerable<(T, int)> WithIndex<T>(this IEnumerable<T> source)
    {
        if (source is null)
        {
            yield break;
        }

        int index = 0;

        foreach (var item in source)
        {
            yield return (item, index);
            index += 1;
        }
    }
}